#include "DatabaseMgr.h"
#include <future>
#include "CMysqlTableCache.h"
#include "MysqlDatabasePool.h"

#define SETUP_WORLD_DB(Type) \
	CMysqlTableCache<Type>::SetDB(sMysqlDatabasePool.GetWorldDB())
#define SETUP_ACTVT_DB(Type) \
	CMysqlTableCache<Type>::SetDB(sMysqlDatabasePool.GetActvtDB())

#define REGISTER_TABLE(Type,Value) \
	do { \
		assert(reinterpret_cast<ITableCache*>(Value) == Value); \
		RegisterTablePtr(GetTableName<Type>(), (ITableCache**)(&Value)); \
	} while (0)

#define MACRO_CREATE_DELETE_LOAD_GET_TABLE(Type,Value) \
	template<> void DatabaseMgr::CreateTable<Type>() { \
		Value = new CMysqlTableCache<Type>(); \
		REGISTER_TABLE(Type, Value); \
	} \
	template<> void DatabaseMgr::DeleteTable<Type>() { \
		delete Value; \
	} \
	template<> void DatabaseMgr::AsyncLoadTable<Type>() { \
		return AsyncLoadTable(Value); \
	} \
	template<> const CTableCache<Type>* DatabaseMgr::GetTable() const { \
		return Value; \
	}
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(actvt_string_text_list, m_pActvtStringTextTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(string_text_list, m_pStringTextTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(Configure, m_pConfigureTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(Scriptable, m_pScriptableTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(GameTime, m_pGameTimeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(MapInfo, m_pMapInfoTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(MapZone, m_pMapZoneTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(MapGraveyard, m_pMapGraveyardTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(TeleportPoint, m_pTeleportPointTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(WayPoint, m_pWayPointTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(LandmarkPoint, m_pLandmarkPointTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(ItemPrototype, m_pItemPrototypeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(ItemEquipPrototype, m_pItemEquipPrototypeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(CharPrototype, m_pCharPrototypeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(CreatureSpawn, m_pCreatureSpawnTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(SObjPrototype, m_pSObjPrototypeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(StaticObjectSpawn, m_pStaticObjectSpawnTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(LootSet, m_pLootSetTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(LootSetGroup, m_pLootSetGroupTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(LootSetGroupItem, m_pLootSetGroupItemTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(LootSetGroupCheque, m_pLootSetGroupChequeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(SpellInfo, m_pSpellInfoTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(SpellLevelInfo, m_pSpellLevelInfoTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(SpellLevelEffectInfo, m_pSpellLevelEffectInfoTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(QuestPrototype, m_pQuestPrototypeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(QuestCreatureVisible, m_pQuestCreatureVisibleTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(PlayerBase, m_pPlayerBaseTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(PlayerAttribute, m_pPlayerAttributeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(CreatureAttribute, m_pCreatureAttributeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(PayShop, m_pPayShopTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(ShopPrototype, m_pShopPrototypeTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(OperatingActivity, m_pOperatingActivityTbl);
	MACRO_CREATE_DELETE_LOAD_GET_TABLE(OperatingActivityUIType, m_pOperatingActivityUITypeTbl);
#undef MACRO_CREATE_DELETE_LOAD_GET_TABLE

DatabaseMgr::DatabaseMgr()
: m_lang(0)
{
	CreateTable<actvt_string_text_list>();
	CreateTable<string_text_list>();
	CreateTable<Configure>();
	CreateTable<Scriptable>();
	CreateTable<GameTime>();
	CreateTable<MapInfo>();
	CreateTable<MapZone>();
	CreateTable<MapGraveyard>();
	CreateTable<TeleportPoint>();
	CreateTable<WayPoint>();
	CreateTable<LandmarkPoint>();
	CreateTable<ItemPrototype>();
	CreateTable<ItemEquipPrototype>();
	CreateTable<CharPrototype>();
	CreateTable<CreatureSpawn>();
	CreateTable<SObjPrototype>();
	CreateTable<StaticObjectSpawn>();
	CreateTable<LootSet>();
	CreateTable<LootSetGroup>();
	CreateTable<LootSetGroupItem>();
	CreateTable<LootSetGroupCheque>();
	CreateTable<SpellInfo>();
	CreateTable<SpellLevelInfo>();
	CreateTable<SpellLevelEffectInfo>();
	CreateTable<QuestPrototype>();
	CreateTable<QuestCreatureVisible>();
	CreateTable<PlayerBase>();
	CreateTable<PlayerAttribute>();
	CreateTable<CreatureAttribute>();
	CreateTable<PayShop>();
	CreateTable<ShopPrototype>();
	CreateTable<OperatingActivity>();
	CreateTable<OperatingActivityUIType>();
}

DatabaseMgr::~DatabaseMgr()
{
	DeleteTable<actvt_string_text_list>();
	DeleteTable<string_text_list>();
	DeleteTable<Configure>();
	DeleteTable<Scriptable>();
	DeleteTable<GameTime>();
	DeleteTable<MapInfo>();
	DeleteTable<MapZone>();
	DeleteTable<MapGraveyard>();
	DeleteTable<TeleportPoint>();
	DeleteTable<WayPoint>();
	DeleteTable<LandmarkPoint>();
	DeleteTable<ItemPrototype>();
	DeleteTable<ItemEquipPrototype>();
	DeleteTable<CharPrototype>();
	DeleteTable<CreatureSpawn>();
	DeleteTable<SObjPrototype>();
	DeleteTable<StaticObjectSpawn>();
	DeleteTable<LootSet>();
	DeleteTable<LootSetGroup>();
	DeleteTable<LootSetGroupItem>();
	DeleteTable<LootSetGroupCheque>();
	DeleteTable<SpellInfo>();
	DeleteTable<SpellLevelInfo>();
	DeleteTable<SpellLevelEffectInfo>();
	DeleteTable<QuestPrototype>();
	DeleteTable<QuestCreatureVisible>();
	DeleteTable<PlayerBase>();
	DeleteTable<PlayerAttribute>();
	DeleteTable<CreatureAttribute>();
	DeleteTable<PayShop>();
	DeleteTable<ShopPrototype>();
	DeleteTable<OperatingActivity>();
	DeleteTable<OperatingActivityUIType>();
	for (auto pTable : m_obsoleteTables) {
		delete pTable;
	}
}

void DatabaseMgr::SetupActvtDB()
{
	SETUP_ACTVT_DB(actvt_string_text_list);
	SETUP_ACTVT_DB(OperatingActivity);
	SETUP_ACTVT_DB(OperatingActivityUIType);
}

void DatabaseMgr::SetupWorldDB()
{
	SETUP_WORLD_DB(string_text_list);
	SETUP_WORLD_DB(Configure);
	SETUP_WORLD_DB(Scriptable);
	SETUP_WORLD_DB(GameTime);
	SETUP_WORLD_DB(MapInfo);
	SETUP_WORLD_DB(MapZone);
	SETUP_WORLD_DB(MapGraveyard);
	SETUP_WORLD_DB(TeleportPoint);
	SETUP_WORLD_DB(WayPoint);
	SETUP_WORLD_DB(LandmarkPoint);
	SETUP_WORLD_DB(ItemPrototype);
	SETUP_WORLD_DB(ItemEquipPrototype);
	SETUP_WORLD_DB(CharPrototype);
	SETUP_WORLD_DB(CreatureSpawn);
	SETUP_WORLD_DB(SObjPrototype);
	SETUP_WORLD_DB(StaticObjectSpawn);
	SETUP_WORLD_DB(LootSet);
	SETUP_WORLD_DB(LootSetGroup);
	SETUP_WORLD_DB(LootSetGroupItem);
	SETUP_WORLD_DB(LootSetGroupCheque);
	SETUP_WORLD_DB(SpellInfo);
	SETUP_WORLD_DB(SpellLevelInfo);
	SETUP_WORLD_DB(SpellLevelEffectInfo);
	SETUP_WORLD_DB(QuestPrototype);
	SETUP_WORLD_DB(QuestCreatureVisible);
	SETUP_WORLD_DB(PlayerBase);
	SETUP_WORLD_DB(PlayerAttribute);
	SETUP_WORLD_DB(CreatureAttribute);
	SETUP_WORLD_DB(PayShop);
	SETUP_WORLD_DB(ShopPrototype);
}

bool DatabaseMgr::AsyncLoadAllTables(size_t threads)
{
	AsyncLoadTable<actvt_string_text_list>();
	AsyncLoadTable<string_text_list>();
	AsyncLoadTable<Configure>();
	AsyncLoadTable<Scriptable>();
	AsyncLoadTable<GameTime>();
	AsyncLoadTable<MapInfo>();
	AsyncLoadTable<MapZone>();
	AsyncLoadTable<MapGraveyard>();
	AsyncLoadTable<TeleportPoint>();
	AsyncLoadTable<WayPoint>();
	AsyncLoadTable<LandmarkPoint>();
	AsyncLoadTable<ItemPrototype>();
	AsyncLoadTable<ItemEquipPrototype>();
	AsyncLoadTable<CharPrototype>();
	AsyncLoadTable<CreatureSpawn>();
	AsyncLoadTable<SObjPrototype>();
	AsyncLoadTable<StaticObjectSpawn>();
	AsyncLoadTable<LootSet>();
	AsyncLoadTable<LootSetGroup>();
	AsyncLoadTable<LootSetGroupItem>();
	AsyncLoadTable<LootSetGroupCheque>();
	AsyncLoadTable<SpellInfo>();
	AsyncLoadTable<SpellLevelInfo>();
	AsyncLoadTable<SpellLevelEffectInfo>();
	AsyncLoadTable<QuestPrototype>();
	AsyncLoadTable<QuestCreatureVisible>();
	AsyncLoadTable<PlayerBase>();
	AsyncLoadTable<PlayerAttribute>();
	AsyncLoadTable<CreatureAttribute>();
	AsyncLoadTable<PayShop>();
	AsyncLoadTable<ShopPrototype>();
	AsyncLoadTable<OperatingActivity>();
	AsyncLoadTable<OperatingActivityUIType>();
	return WaitFinishTasks(m_asyncLoadTables, threads);
}

void DatabaseMgr::AsyncLoadTable(ITableCache* pTableCache)
{
	m_asyncLoadTables.Enqueue([=]() {
		return pTableCache->LoadData();
	});
}

bool DatabaseMgr::WaitFinishTasks(
	ThreadSafeQueue<std::function<bool()>>& tasks, size_t threads)
{
	bool isOK = true;
	auto Worker = [&]() {
		std::function<bool()> func;
		while (tasks.Dequeue(func)) {
			isOK &= func();
		}
	};

	std::vector<std::future<void>> futures(threads);
	for (size_t i = 0; i < 1 || i < threads; ++i) {
		futures[i] = std::async(std::launch::async, Worker);
	}
	for (size_t i = 0; i < 1 || i < threads; ++i) {
		futures[i].wait();
	}

	return isOK;
}

void DatabaseMgr::RegisterTablePtr(
	const std::string_view& tblName, ITableCache** pTableCache)
{
	m_registerTables.emplace(tblName, pTableCache);
}

bool DatabaseMgr::ReloadTable(const std::string_view& name)
{
	auto itr = m_registerTables.find(name);
	if (itr == m_registerTables.end()) {
		WLOG("Can't find table `%s`.", name.data());
		return false;
	}

	auto tblPPtr = itr->second;
	if (*tblPPtr == NULL) {
		WLOG("Can't find metatable `%s`.", name.data());
		return false;
	}

	auto newTblPtr = (*tblPPtr)->New();
	if (!newTblPtr->LoadData()) {
		WLOG("Reload table `%s` failed.", name.data());
		delete newTblPtr;
		return false;
	}

	m_obsoleteTables.push_back(*tblPPtr);
	*tblPPtr = newTblPtr;

	return true;
}

void DatabaseMgr::SetLang(int lang)
{
	DBGASSERT(lang >= 0 && lang < LANG_MAX);
	m_lang = lang;
}

#define GetText4Lang(Entry,Lang) (&Entry->stringEN)[Lang]
const std::string& DatabaseMgr::GetText(uint32 id, STRING_TEXT_TYPE type)
{
	auto pEntry = m_pStringTextTbl->GetEntry(MakeTextID(id, type));
	return pEntry != NULL ? GetText4Lang(pEntry, m_lang) : emptyString;
}
const std::string& DatabaseMgr::GetActvtText(uint32 id, STRING_TEXT_TYPE type)
{
	auto pEntry = m_pActvtStringTextTbl->GetEntry(MakeTextID(id, type));
	return pEntry != NULL ? GetText4Lang(pEntry, m_lang) : emptyString;
}
#undef GetText4Lang

const std::string emptyString;
const std::string_view emptyStringView;
